/*******************************************************************************
* Copyright (c) 2008, Jeferson Jose de Miranda                                 *
* UDESC - Universidade do Estado de Santa Catarina                             *
* All rights reserved.                                                         *
*                                                                              *
* Redistribution and use in source and binary forms, with or without           *
* modification, are permitted provided that the following conditions are met:  *
*     * Redistributions of source code must retain the above copyright         *
*       notice, this list of conditions and the following disclaimer.          *
*     * Redistributions in binary form must reproduce the above copyright      *
*       notice, this list of conditions and the following disclaimer in the    *
*       documentation and/or other materials provided with the distribution.   *
*     * Neither the name of the Universidade do Estado de Santa Catarina nor   *
*       the names of its contributors may be used to endorse or promote        *
*       products derived from this software without specific prior written     *
*       permission.                                                            *
*                                                                              *
* THIS SOFTWARE IS PROVIDED BY JEFERSON JOSE DE MIRANDA "AS IS" AND ANY        *
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED    *
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE       *
* DISCLAIMED. IN NO EVENT SHALL JEFERSON JOSE DE MIRANDA BE LIABLE FOR ANY     *
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES   *
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; *
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND  *
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF     *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.            *
*******************************************************************************/

/**
 * \file Screen.hpp
 *
 * \brief Grabs frames from the webcam and stores the result.
 *
 * \author Jeferson Jose de Miranda
 */

#ifndef SCREEN_HPP
#define SCREEN_HPP

// Move-In base classes.
#include "Camera.hpp"
#include "RGBImage.hpp"
#include "GreyLevelImage.hpp"
// Move-In core classes.
#include "FrameDifference.hpp"
#include "SkinColor.hpp"
#include "FrameFeatures.hpp"
#include "Flow.hpp"
// Move-In ROI management.
#include "ROIGroup.hpp"

#define PRESENCE 0
#define MOVEMENT 1
#define ALL      2

#define NONE                  10
#define INVERTED              11
#define MIRRORED              12
#define INVERTED_AND_MIRRORED 13

#define SIMPLE_RGB true
#define NORMALIZED_RGB false

namespace MoveIn
{
    class Screen : public FrameFeatures,
                   public FrameDifference,
                   public SkinColor
    {
        private:
        Camera *camera;
        RGBImage *reference;
        RGBImage *previous;
        RGBImage *current;
        GreyLevelImage *segmented;

        uchar mode;
        uchar orientation;

        unsigned short int width;
        unsigned short int height;

        public:
        Screen::Screen(int index = -1,
                       uchar mode = ALL,
                       uchar orientation = INVERTED_AND_MIRRORED);
        ~Screen();

        void update();
        void init();
        void reset();

        unsigned short int getWidth();
        unsigned short int getHeight();

        RGBImage* getReferenceImage();
        RGBImage* getPreviousImage();
        RGBImage* getCurrentImage();
        GreyLevelImage* getSegmentedImage();

        IplImage* getReferenceIplImage();
        IplImage* getPreviousIplImage();
        IplImage* getCurrentIplImage();
        IplImage* getSegmentedIplImage();

        void presence(bool type = SIMPLE_RGB,
                      bool gray_level = BINARY,
                      unsigned int threshold = 40,
                      ROIGroup *roi_group = NULL);

        void movement(bool gray_level = BINARY,
                      unsigned int threshold = 40,
                      ROIGroup *roi_group = NULL);

        FlowVectors* flow(CvRect *ROI = NULL);

        void skinColor(bool type = SIMPLE_RGB,
                       ROIGroup *roi_group = NULL);

        void setOrientation(uchar orientation);
    };

/*******************************************************************************
* MoveIn::Screen::Screen                                                       *
*******************************************************************************/

    Screen::Screen(int index,
                   uchar mode,
                   uchar orientation)
    {
        this->mode = mode;
        this->orientation = orientation;

        camera = NULL;
        reference = NULL;
        previous = NULL;
        current = NULL;
        segmented = NULL;

        try
        {
            camera = new Camera(index);
            // This is necessary to initialize
            // the camera dimensions parameters.
            camera->initialize();
            width = camera->getWidth();
            height = camera->getHeight();
        }
        catch(std::exception *e)
        {
            throw e;
        }

        if(camera != NULL)
        {
            try
            {
                if(mode == PRESENCE || mode == ALL)
                {
                    reference = new RGBImage(width,
                                             height);
                }

                if(mode == MOVEMENT || mode == ALL)
                {
                    previous = new RGBImage(width,
                                            height);
                }

                current = new RGBImage(width,
                                       height);

                segmented = new GreyLevelImage(width,
                                               height);
            }
            catch(std::exception *e)
            {
                throw e;
            }
        }
    }

/*******************************************************************************
* MoveIn::Screen::Screen                                                       *
*******************************************************************************/

    Screen::~Screen()
    {
        if(reference != NULL)
        {
            delete reference;
        }
        if(previous != NULL)
        {
            delete previous;
        }
        if(current != NULL)
        {
            delete current;
        }
        if(reference != NULL)
        {
            delete reference;
        }
    }

/*******************************************************************************
* MoveIn::Screen::update                                                       *
*******************************************************************************/

    void Screen::update()
    {
        if(mode == ALL || mode == MOVEMENT)
        {
            previous->copyImage(current);
        }

        current->copyImage(camera->grabFrame());
        if(orientation == INVERTED)
        {
            current->invert();
        }
        else if(orientation == MIRRORED)
        {
            current->mirror();
        }
        else if(orientation == INVERTED_AND_MIRRORED)
        {
            current->invertAndMirror();
        }
    }

/*******************************************************************************
* MoveIn::Screen::init                                                         *
*******************************************************************************/

    void Screen::init()
    {
        reference->copyImage(camera->grabFrame());
        if(orientation == INVERTED)
        {
            reference->invert();
        }
        else if(orientation == MIRRORED)
        {
            reference->mirror();
        }
        else if(orientation == INVERTED_AND_MIRRORED)
        {
            reference->invertAndMirror();
        }
    }

/*******************************************************************************
* MoveIn::Screen::reset                                                        *
*******************************************************************************/

    void Screen::reset()
    {
        if(reference != NULL)
        {
            reference->reset();
        }
        if(previous != NULL)
        {
            previous->reset();
        }
        if(current != NULL)
        {
            current->reset();
        }
        if(reference != NULL)
        {
            reference->reset();
        }
    }

/*******************************************************************************
* MoveIn::Screen::getWidth                                                     *
*******************************************************************************/

    unsigned short int Screen::getWidth()
    {
        return width;
    }

/*******************************************************************************
* MoveIn::Screen::getHeight                                                    *
*******************************************************************************/

    unsigned short int Screen::getHeight()
    {
        return height;
    }

/*******************************************************************************
* MoveIn::Screen::getReferenceImage                                            *
*******************************************************************************/

    RGBImage* Screen::getReferenceImage()
    {
        return reference;
    }

/*******************************************************************************
* MoveIn::Screen::getPreviousImage                                             *
*******************************************************************************/

    RGBImage* Screen::getPreviousImage()
    {
        return previous;
    }

/*******************************************************************************
* MoveIn::Screen::getCurrentImage                                              *
*******************************************************************************/

    RGBImage* Screen::getCurrentImage()
    {
        return current;
    }

/*******************************************************************************
* MoveIn::Screen::getSegmentedImage                                            *
*******************************************************************************/

    GreyLevelImage* Screen::getSegmentedImage()
    {
        return segmented;
    }

/*******************************************************************************
* MoveIn::Screen::getReferenceIplImage                                         *
*******************************************************************************/

    IplImage* Screen::getReferenceIplImage()
    {
        return reference->getIplImage();
    }

/*******************************************************************************
* MoveIn::Screen::getPreviousIplImage                                          *
*******************************************************************************/

    IplImage* Screen::getPreviousIplImage()
    {
        return previous->getIplImage();
    }

/*******************************************************************************
* MoveIn::Screen::getCurrentIplImage                                           *
*******************************************************************************/

    IplImage* Screen::getCurrentIplImage()
    {
        return current->getIplImage();
    }

/*******************************************************************************
* MoveIn::Screen::getSegmentedIplImage                                         *
*******************************************************************************/

    IplImage* Screen::getSegmentedIplImage()
    {
        return segmented->getIplImage();
    }

/*******************************************************************************
* MoveIn::Screen::presence                                                     *
*******************************************************************************/

    void Screen::presence(bool type,
                          bool gray_level,
                          unsigned int threshold,
                          ROIGroup *roi_group)
    {
        if(type == SIMPLE_RGB)
        {
            RGBDifference(reference,
                          current,
                          segmented,
                          gray_level,
                          threshold,
                          NULL);
        }
        else
        {
            normalizedRGBDifference(reference,
                                    current,
                                    segmented,
                                    gray_level,
                                    threshold,
                                    NULL);
        }
    }

/*******************************************************************************
* MoveIn::Screen::movement                                                     *
*******************************************************************************/

    void Screen::movement(bool gray_level,
                          unsigned int threshold,
                          ROIGroup *roi_group)
    {
        if(roi_group == NULL)
        {
            RGBDifference(previous,
                          current,
                          segmented,
                          gray_level,
                          threshold,
                          NULL);
        }
        else
        {
            CvRect roi;
            for(unsigned int i=0; i<roi_group->size(); i++)
            {
                if(roi_group->position(current, i) == INSIDE)
                {
                    roi = roi_group->getROI(i);
                    RGBDifference(previous,
                                  current,
                                  segmented,
                                  gray_level,
                                  threshold,
                                  &roi);
                }
            }
        }
    }

/*******************************************************************************
* MoveIn::Screen::flow                                                         *
*******************************************************************************/

    FlowVectors* Screen::flow(CvRect *ROI)
    {
        return NULL;
    }

/*******************************************************************************
* MoveIn::Screen::skinColor                                                    *
*******************************************************************************/

    void Screen::skinColor(bool type,
                           ROIGroup *roi_group)
    {
        if(type == SIMPLE_RGB)
        {
            SkinColor::skinColor(current, segmented);
        }
        else
        {
            normalizedSkinColor(current, segmented);
        }
    }

/*******************************************************************************
* MoveIn::Screen::setOrientation                                               *
*******************************************************************************/

    void Screen::setOrientation(uchar orientation)
    {
        this->orientation = orientation;
    }
}

#endif
